Wielowymiarowa analiza danych oraz analiza koszykowa z wykorzystaniem algorytmu apriori na podstawie przedsiębiorstwa X
Import niezbędnych bibliotek m.in.: pandas, numpy i matplotlib
import numpy as np
import pandas as pd
import plotly.express as px
import seaborn as sns
import scipy as sp
from scipy.stats import shapiro
from mlxtend.frequent_patterns import apriori, association_rules
import random
%matplotlib inline
import matplotlib.pyplot as plt
Pobieramy dane między innymi o zamównieniach, klientach, subskrybentach, produktach, kampaniach itd. Następnie wywołując metodę head() oraz shape, sprawdzam poprawność zaimportowanych danych (m.in. odkodowanie, separatory, wielkość liter, spójność). Wstępnie zapoznaję się z danymi badanego przedsiębiorstwa.
orders = pd.read_csv('Data /orders.txt', sep='\t', encoding='unicode_escape')
campaigns = pd.read_csv('Data /campaigns.txt', encoding='unicode_escape', sep='\t')
customers = pd.read_csv('Data /customers.txt', encoding='unicode_escape', sep='\t')
subscribers = pd.read_csv('Data /Subscribers.txt', encoding='unicode_escape', sep='\t')
products = pd.read_csv('Data /products.txt', encoding='unicode_escape', sep='\t')
products.columns = products.columns.str.lower()
orderlines = pd.read_csv('Data /orderlines.txt',encoding='unicode_escape', delimiter='\t')
calendar = pd.read_csv('Data /calendar.txt', encoding='unicode_escape', sep='\t')
Szczegóły bazy danych o zamówień oraz jej wielkość
print(orders.shape)
orders.head(2)
(192983, 11)
| orderid | customerid | campaignid | orderdate | city | state | zipcode | paymenttype | totalprice | numorderlines | numunits | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1002854 | 45978 | 2141 | 2009-10-13 00:00:00 | NEWTON | MA | 02459 | VI | 190.0 | 3 | 3 |
| 1 | 1002855 | 125381 | 2173 | 2009-10-13 00:00:00 | NEW ROCHELLE | NY | 10804 | VI | 10.0 | 1 | 1 |
Szczegóły bazy danych o kampaniach oraz jej wielkość
print(campaigns.shape)
campaigns.head(2)
(239, 5)
| campaignid | campaignname | channel | discount | freeshippingflag | |
|---|---|---|---|---|---|
| 0 | 2001 | NaN | PARTNER | 0 | N |
| 1 | 2002 | NaN | AD | 0 | N |
Szczegóły bazy danych o klientach oraz jej wielkość
print(customers.shape)
customers.head(2)
(189559, 4)
| customerid | householdid | gender | firstname | |
|---|---|---|---|---|
| 0 | 174596 | 53949999 | M | DANIEL |
| 1 | 68239 | 49927024 | M | JIM |
Szczegóły bazy danych o subksrybcjach oraz jej wielkość
print(subscribers.shape)
subscribers.head(2)
(5068035, 10)
| customer_id | rate_plan | monthly_fee | market | channel | start_date | stop_date | stop_type | tenure | censored | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 2 | Top | 150.0 | Gotham | Dealer | 2001-09-16 | NaN | NaN | 1929.0 | 1 |
| 1 | 52 | Bottom | 25.0 | Metropolis | 1997-01-13 | 2004-12-17 | V | 2895.0 | 0 |
Szczegóły bazy danych o produktach oraz jej wielkość
print(products.shape)
products.head(2)
(4040, 6)
| productid | productname | productgroupcode | productgroupname | instockflag | fullprice | |
|---|---|---|---|---|---|---|
| 0 | 10001 | NaN | CA | CALENDAR | N | 15 |
| 1 | 10002 | NaN | CA | CALENDAR | N | 10 |
Szczegóły bazy danych orderlines oraz jej wielkość
print(orderlines.shape)
orderlines.head(2)
(286017, 8)
| orderlineid | orderid | productid | shipdate | billdate | unitprice | numunits | totalprice | |
|---|---|---|---|---|---|---|---|---|
| 0 | 1010561 | 1006414 | 10834 | 2011-03-07 00:00:00 | 2011-03-08 00:00:00 | 18.0 | 1 | 18.0 |
| 1 | 1010562 | 1006541 | 11052 | 2011-01-19 00:00:00 | 2011-01-20 00:00:00 | 10.0 | 2 | 20.0 |
Szczegóły bazy danych kalendarz oraz jej wielkość.
print(calendar.shape)
calendar.head(2)
(36890, 27)
| DATE | ISO | datenum | DOW | dowint | Year | Month | dom | monthstr | doy | ... | numholidays | holidayname | holidaytype | national | minor | christian | jewish | muslim | chinese | other | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1950-01-01 00:00:00 | 1950-01-01 | 18264 | Sun | 1 | 1950 | 1 | 1 | Jan | 1 | ... | 1 | New Year's Day | national | New Year's Day | NaN | NaN | NaN | NaN | NaN | NaN |
| 1 | 1950-01-02 00:00:00 | 1950-01-02 | 18265 | Mon | 2 | 1950 | 1 | 2 | Jan | 2 | ... | 0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 rows × 27 columns
Zaimportowano dane dotyczące sprzedaży analizowanego przedsiębiorstwa, z których dowiadujemy się przede wszystkim o rozmiarze baz danych - z ilu wierszy oraz kolumn się składa oraz jakie dane mieszczą się w kolejnych kolumnach. Przedsiębiorstwo prowadząc sprzedaż do każdego zamówienia, któremu nadawany jest indywidualny numer gromadzi jeszcze inne informacje w postaci numeru identyfikacyjnego klienta, numeru kampani, daty zamówienia, miasta/stanu/kodu pocztowego z którego zostało zamówienie złożone, metody płatności, wartości całego zamówienia jak również ilości zamówionych produktów.
Na początek rozważań sprawdzam, z jakiego czasookresu są dane dotyczące zamówień - z ilu pełnych lat posiadamy dane. Widzimy, że dla lat od 2010 do 2015 mamy dane dla pełnego roku.
print('Najstarsze zamówienie:', min(orders.orderdate))
print('Najnowsze zamówienie:', max(orders.orderdate))
Najstarsze zamówienie: 2009-10-04 00:00:00 Najnowsze zamówienie: 2016-09-20 00:00:00
Na potrzeby prowadzonej analizy, łączę dane tworząc duży zbiór danych pod nazwą all_coop. Do baz danych pod nazwą all_coop oraz orderlines dodaję kolumnę year, i konwertuję dane na datetime. Dla pozostałych kilku baz dodaję kolumnę to_count w celu przeprowadzenia obliczeń w dalszej części pracy.
customers['to_count'] = 1
orders['to_count'] = 1
customers_orders = customers.merge(orders, left_on='customerid', right_on='customerid')
orderlines_products = orderlines.merge(products, left_on='productid', right_on='productid')
orderlines_products['to_count'] = 1
all_coop = customers_orders.merge(orderlines_products, left_on='orderid', right_on='orderid')
all_coop.orderdate = pd.to_datetime(all_coop.orderdate)
all_coop.shipdate = pd.to_datetime(all_coop.shipdate)
all_coop.billdate = pd.to_datetime(all_coop.billdate)
orderlines.billdate = pd.to_datetime(orderlines.billdate)
orderlines.shipdate = pd.to_datetime(orderlines.shipdate)
pd.options.display.float_format = '${:,.2f}'.format
orderlines['year'] = orderlines.billdate.dt.year
all_coop['year'] = all_coop.orderdate.dt.year
campaigns['to_count'] = 1
subscribers['to_count'] = 1
customers_orders.head(2)
| customerid | householdid | gender | firstname | to_count_x | orderid | campaignid | orderdate | city | state | zipcode | paymenttype | totalprice | numorderlines | numunits | to_count_y | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 174596 | 53949999 | M | DANIEL | 1 | 1391159 | 2204 | 2015-09-27 00:00:00 | MONTVALE | NJ | 07645 | AE | $44.95 | 1 | 1 | 1 |
| 1 | 68239 | 49927024 | M | JIM | 1 | 1391160 | 2237 | 2015-09-24 00:00:00 | NEW YORK | NY | 10036 | DB | $0.00 | 1 | 100 | 1 |
orderlines_products.head(2)
| orderlineid | orderid | productid | shipdate | billdate | unitprice | numunits | totalprice | year | productname | productgroupcode | productgroupname | instockflag | fullprice | to_count | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1010561 | 1006414 | 10834 | 2011-03-07 | 2011-03-08 | $18.00 | 1 | $18.00 | 2011 | $nan | BK | BOOK | N | 25 | 1 |
| 1 | 1010589 | 1008588 | 10834 | 2011-03-08 | 2011-03-09 | $18.00 | 1 | $18.00 | 2011 | $nan | BK | BOOK | N | 25 | 1 |
all_coop.head(2)
| customerid | householdid | gender | firstname | to_count_x | orderid | campaignid | orderdate | city | state | ... | unitprice | numunits_y | totalprice_y | year | productname | productgroupcode | productgroupname | instockflag | fullprice | to_count | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 174596 | 53949999 | M | DANIEL | 1 | 1391159 | 2204 | 2015-09-27 | MONTVALE | NJ | ... | $44.95 | 1 | $44.95 | 2015 | $nan | AR | ARTWORK | Y | 44 | 1 |
| 1 | 68239 | 49927024 | M | JIM | 1 | 1391160 | 2237 | 2015-09-24 | NEW YORK | NY | ... | $0.00 | 100 | $0.00 | 2015 | $nan | OC | OCCASION | Y | 25 | 1 |
2 rows × 30 columns
Sprawdzam jaki jest całkowity przychód badanego przedsiębiorst. W badanym okresie wyniósł 13.708.896,30 z największym udziałem w tej sumie produkty z grupy ARTWORK ,których łączna wartość sprzedaży wyniosła 9.137.891,64
orderlines.drop(columns=['orderlineid', 'orderid', 'productid', 'shipdate', 'billdate',
'unitprice', 'numunits', 'year']).sum()
totalprice $13,708,896.30 dtype: float64
Sprawdzam również strukturę klientów ze względu na płeć, obliczając liczebność każdej z grupy, a następnie rzutując ją na całość populacji. Stąd otrzymujemy informację, że mężczyźni stanowią 55,6% udziału a kobiety 44,4%, a zatem mamy niewielką dysproporcję ze względu na płeć pośród klientów.
# operacja wykonana na początku notebooka - customers['to_count'] = 1
customers.drop(columns=['customerid','householdid','firstname']).groupby(by=['gender']).sum()
| to_count | |
|---|---|
| gender | |
| F | 76874 |
| M | 96481 |
(customers.gender == 'M').sum()/customers.gender.count()
0.5565515848980416
(customers.gender == 'F').sum()/customers.gender.count()
0.4434484151019584
customers_gender = customers.drop(columns=['customerid','householdid','firstname']).groupby(by=['gender']).sum().rename(columns = {'to_count':'quantity'})
labels = ('Female','Man')
sizes = customers_gender['quantity']
explode = (0,0)
fig1, ax1 = plt.subplots(figsize = (6,6))
ax1.pie(sizes, explode=explode, labels=labels, autopct='%1.2f%%', colors =['orange','green'],
shadow=False, startangle=90)
ax1.axis('equal')
plt.title('Struktura klientów ze względu na płeć', fontsize = 17, color = 'black')
plt.show()
Sprawdzam również popularność kanałów, którymi prowadzone są kampanie. Z przeprowadzonych przeliczeń wnioskuję, że najpopularniejszym jest AD, a następnie MAIL oraz PARTNER.
# operacja wykonana na początku notebooka - campaigns['to_count'] = 1
campaigns.drop(columns=['campaignid','campaignname','discount','freeshippingflag']).groupby(by=['channel']).sum()
| to_count | |
|---|---|
| channel | |
| AD | 80 |
| BULK | 3 |
| CATALOG | 4 |
| CONFERENCE | 1 |
| 14 | |
| EMPLOYEE | 9 |
| INSERT | 18 |
| INTERNAL | 1 |
| 46 | |
| PARTNER | 39 |
| REFERRAL | 1 |
| SURVEY | 1 |
| WEB | 22 |
campaigns_plot = campaigns.drop(columns=['campaignid','campaignname','discount','freeshippingflag'])\
.groupby(by=['channel']).sum().rename(columns = {'to_count':'quantity'})
explode = (0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1)
fig1, ax1 = plt.subplots(figsize=(10,10))
ax1.pie(campaigns_plot['quantity'], explode=explode, labels=campaigns_plot.index, autopct='%0.1f%%',
shadow=False, startangle=10)
plt.title('Udział procentowy każdego z kanałów', fontsize = 20, color = 'black')
plt.show()
Określam popularność metody płatności, dodając dodatkową kolumnę do bazy danych orders, a następnie poprzez grupowanie zliczam liczebność w grupach. W ten sposób otrzymuję informację, że najpopularniejszą formą płatności jest ta o oznaczniu VI z procentowym udziałem pośród wszystkich transaki na poziomie niespełna 40%, a następnie praktycznie równo na drugim i trzecim miejscu są metoda AE oraz MC.
# operacja wykonanan na początku notebooka - orders['to_count'] = 1
paymenttype_plot = orders.drop(columns =['orderid','customerid','campaignid',\
'totalprice','numorderlines','numunits'\
]).groupby(by='paymenttype').sum()
paymenttype_plot
| to_count | |
|---|---|
| paymenttype | |
| ?? | 313 |
| AE | 47382 |
| DB | 12739 |
| MC | 47318 |
| OC | 8214 |
| VI | 77017 |
labels = paymenttype_plot.index
sizes = paymenttype_plot['to_count']
explode = (0.2, 0.2, 0.2, 0.2, 0.2, 0.2)
fig1, ax1 = plt.subplots(figsize=(8,8))
ax1.pie(sizes, explode=explode, labels=labels, autopct='%2.2f%%',
shadow=False, startangle=10)
ax1.axis('equal')
plt.title('Udział procentowy każdej z form płatności', fontsize = 18, color = 'black')
plt.legend(labels)
plt.show()
Następnie sprawdzam w których miastach firma realizuje największe przychody. Dla tego celu tworzę nową zmienną, dla której pozbywam się nie istotnych z tego punktu widzenia danych, a następnie sumuję oraz układam dane malejąco pokazując pierwsze 12 rekordów. Dostajemy informację, że miastem, które generuje najwyższy przychód dla firmy jest New York i jest to wartość dla całego badanego okresu na poziomie 1 571 296,06 dla 17 753 transakcji, co daje nam średnią wartość pojedynczej transakcji na poziomie 88,50.
pd.options.display.float_format = '${:,.2f}'.format
city_mv = orders.drop(columns =['orderid','customerid','campaignid','orderdate','zipcode','numorderlines','numunits']).groupby(by='city').sum()
city_mv_plot = city_mv.sort_values(by = ['totalprice'], ascending = False).head(12)
city_mv_plot
| totalprice | to_count | |
|---|---|---|
| city | ||
| NEW YORK | $1,571,296.06 | 17753 |
| BROOKLYN | $234,760.02 | 4078 |
| CHICAGO | $219,478.89 | 2285 |
| WASHINGTON | $206,097.78 | 1977 |
| LOS ANGELES | $194,014.83 | 1609 |
| SAN FRANCISCO | $144,687.92 | 1898 |
| HOUSTON | $137,757.62 | 1614 |
| ATLANTA | $115,784.27 | 1320 |
| DALLAS | $113,732.83 | 1160 |
| SEATTLE | $88,012.00 | 1354 |
| GREENWICH | $76,411.54 | 593 |
| MIAMI | $67,903.61 | 876 |
city_mv_plot.drop(columns=['to_count']).plot(kind='bar',color = 'green',use_index=True, facecolor='g', alpha=0.6, figsize = (10,5) )
plt.title('Wysokość sprzedaży w pierwszych 12 miastach', color = 'black', fontsize=20)
plt.ylabel('Wartość transakcji w mln $', fontsize=20, color= 'black')
plt.xlabel('Miasta', fontsize=20, color= 'black')
Text(0.5, 0, 'Miasta')
print('Średnia wartość transakcji dla miasta New York w $:', 1571296.06/17753)
Średnia wartość transakcji dla miasta New York w $: 88.50876246268237
city_mv_plot1 = city_mv_plot.drop(columns= 'totalprice').rename(columns={'to_count':'number_of_transaction'})
city_mv_plot1.plot(kind='bar',color = 'blue',use_index=True, facecolor='b', alpha=0.9, figsize = (10,5) )
plt.title('12 miast z największą ilością transakcji', color = 'blue', fontsize=20)
plt.ylabel('Ilość transakcji', fontsize=20, color= 'black')
plt.xlabel('Miasta', fontsize=20, color= 'black')
Text(0.5, 0, 'Miasta')
Pośród subskrybentów sprawdzam udział poszczególnych w całości. W liczbach bezwzględnych dla subskrybcji Bottom jest to 4 128 176, Middle 612 038 oraz Top 327 821. Natomiast jeśli chodzi o udział procentowy w całej populacji to ma się to następująco: Bottom 81,45%, Middle 12,08% oraz Top 6,47%.
# operacja wykonana na początku notebooka - subscribers['to_count'] = 1
subscribers.drop(columns=['customer_id','monthly_fee','market','channel','start_date','stop_date','stop_type','tenure','censored']).groupby(by='rate_plan').count()
| to_count | |
|---|---|
| rate_plan | |
| Bottom | 4128176 |
| Middle | 612038 |
| Top | 327821 |
(subscribers.to_count).count()
5068035
print('Udział rate_plan Bottom w całości:', 4128176/(subscribers.to_count).count()*100)
print('Udział rate_plan Middle w całości:', 612038/(subscribers.to_count).count()*100)
print('Udział rate_plan Top w całości:', 327821/(subscribers.to_count).count()*100)
Udział rate_plan Bottom w całości: 81.45515964274122 Udział rate_plan Middle w całości: 12.07643593621591 Udział rate_plan Top w całości: 6.4684044210428695
Sprawdzam w bazie danych produkty, która grupa jest wiodącą jeśli chodzi o ilość i wartość zawartych z jej udziałem transakcji. Dla tego celu do do bazy danych 'orderlines_products' dodaję kolumnę wypełnioną jedynkami, a następnie poprzez grupowanie zliczam liczebność każdej z grup. Jak widać, najpopularniejszą grupą z ogromną przewagą nad pozostałymi jeśli chodzi o wysokość wygenerowanego przychodu jest grupa produktów ARTWORK ze wartością sprzedaży na poziomie ponad 9 mln dol. przy ponad 56 tys. zawartych transakcji. Pod względem ilości transakcji góruje tutaj grupa produktów z kategorii BOOK, z ilością ponad 113 tys. i wygenerowanym przychodzie na poziomie niespełna 2,5 mln dol. Jest to również druga grupa produktów co do wielkości wygenerowanego przychodu.
# operacja wykonana na początku notebooka - orderlines_products['to_count'] = 1
totalprice_productgroup = orderlines_products.drop(columns=['orderlineid', 'orderid', 'productid', 'shipdate', 'billdate',
'unitprice', 'numunits', 'productname','productgroupcode','instockflag', 'fullprice','year']).groupby(by='productgroupname').sum()
totalprice_productgroup
| totalprice | to_count | |
|---|---|---|
| productgroupname | ||
| APPAREL | $206,489.46 | 12348 |
| ARTWORK | $9,137,891.64 | 56498 |
| BOOK | $2,483,487.15 | 113210 |
| CALENDAR | $270,196.64 | 9872 |
| FREEBIE | $0.00 | 28073 |
| GAME | $361,627.41 | 18469 |
| OCCASION | $1,021,676.05 | 41713 |
| OTHER | $227,527.95 | 5825 |
labels = totalprice_productgroup.index
sizes = totalprice_productgroup['totalprice']
explode = (0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01)
fig1, ax1 = plt.subplots(figsize=(8,8))
ax1.pie(sizes, explode=explode, labels=labels, autopct='%2.2f%%',
shadow=False, startangle=10)
ax1.axis('equal')
plt.title('Udział procentowy w wartości zawartych transakcji kolejnych grup produktowych', fontsize = 15, color = 'black')
plt.show()
labels = totalprice_productgroup.index
sizes = totalprice_productgroup['to_count']
explode = (0.05, 0.1, 0.1, 0.05, 0.05, 0.05, 0.05, 0.05)
fig1, ax1 = plt.subplots(figsize=(7,7))
ax1.pie(sizes, explode=explode, labels=labels, autopct='%2.2f%%',
shadow=False, startangle=10)
ax1.axis('equal')
plt.title('Udział procentowy w ilości zawartych transakcji kolejnych grup produktowych',\
fontsize = 18, color = 'black')
plt.show()
totalprice_productgroup_plot = px.box(orderlines_products, x='productgroupcode', y='totalprice',\
color='productgroupcode', notched=True, log_y=True,
title = 'Rozkład całkowietej ceny w grupach produktowych')
totalprice_productgroup_plot.show()
# operacja wykonana na początku notebooka - orderlines_products['to_count'] = 1
pd.options.display.float_format = '{:,.2f}$'.format
totalprice_gender = all_coop[['totalprice_y','gender','to_count','productgroupname']].groupby(by='gender').sum()
totalprice_gender
| totalprice_y | to_count | |
|---|---|---|
| gender | ||
| F | 4,757,235.12$ | 113496 |
| M | 7,528,691.06$ | 143471 |
all_coop_campaign = all_coop.merge(campaigns, left_on='campaignid',right_on='campaignid')
all_coop_campaign.head(2)
| customerid | householdid | gender | firstname | to_count_x | orderid | campaignid | orderdate | city | state | ... | productgroupcode | productgroupname | instockflag | fullprice | to_count_x | campaignname | channel | discount | freeshippingflag | to_count_y | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 174596 | 53949999 | M | DANIEL | 1 | 1391159 | 2204 | 2015-09-27 | MONTVALE | NJ | ... | AR | ARTWORK | Y | 44 | 1 | nan$ | REFERRAL | 0 | N | 1 |
| 1 | 174623 | 53956468 | F | JEANNETTE | 1 | 1391147 | 2204 | 2015-09-27 | SHERMAN OAKES | CA | ... | AR | ARTWORK | Y | 179 | 1 | nan$ | REFERRAL | 0 | N | 1 |
2 rows × 35 columns
totalprice_campaign = all_coop_campaign[['totalprice_y','channel']].groupby(by='channel').sum()
totalprice_campaign
| totalprice_y | |
|---|---|
| channel | |
| AD | 2,198,083.52$ |
| BULK | 39,840.30$ |
| CATALOG | 61,656.92$ |
| CONFERENCE | 300.00$ |
| 10,853.30$ | |
| EMPLOYEE | 48,252.59$ |
| INSERT | 198,945.37$ |
| INTERNAL | 14,473.50$ |
| 64,674.23$ | |
| PARTNER | 6,002,131.82$ |
| REFERRAL | 141,111.40$ |
| SURVEY | 23.96$ |
| WEB | 4,616,292.71$ |
totalprice_campaign.plot(kind='bar',color = 'green',use_index=True, facecolor='g', alpha=0.6, figsize = (10,5) )
plt.title('Wysokość osiągniego przychodu ze względu na kanał dystrybucji', color = 'black', fontsize=20)
plt.ylabel('Wartość transakcji w mln $', fontsize=20, color= 'black')
plt.xlabel('Kanał dystrybucji', fontsize=20, color= 'black')
Text(0.5, 0, 'Kanał dystrybucji')
Z poniższych danych widać, że popularność konkretnej formy płatności przekłada się również na wielkość przychodu. Trzy najpopularniejsze metody VI, AE oraz MC przekłądają się również na wielkość przychodu.
totalprice_paymenttype = all_coop[['totalprice_y','paymenttype','to_count']].groupby(by='paymenttype').sum()
totalprice_paymenttype
| totalprice_y | to_count | |
|---|---|---|
| paymenttype | ||
| ?? | 1,184.17$ | 375 |
| AE | 4,586,224.99$ | 72162 |
| DB | 424,623.02$ | 17502 |
| MC | 3,251,851.49$ | 69850 |
| OC | 260,948.93$ | 10562 |
| VI | 4,871,807.02$ | 110402 |
Poddano analizie metody płatności ze względu na ilość zawartych transakcji z udziałem każdej z nich w kolejnych analizowanych latach. Widać na dołączonym wykresie, że wymienione powyżej najpopularniejsze metody płatnicze VI, AE oraz MC przez cały badany okres utrzymywały podobny wzrost ilości zawartych z ich udziałem transakcji. Rok 2014 był przełomowym, w kolejnych latach nastąpił spadek ilości transakcji praktycznie dla każdej formy płatności.
paymenttype1 = all_coop.groupby(['year','paymenttype']).sum().to_count_x
paymenttype1_plot = pd.DataFrame(paymenttype1)
paymenttype1_plot_ri = paymenttype1_plot.reset_index()
fig = px.scatter(paymenttype1_plot_ri, x = 'year', y = 'to_count_x', color = 'paymenttype', trendline='ols', template='simple_white')
fig.update_layout(barmode = 'group')
fig.layout.title = 'Ilość zawartych transakcji ze względu na formę płatności'
fig.layout.xaxis.title = 'Lata'
fig.layout.yaxis.title = 'Ilość zawartych transakcji'
fig.show()
Dane z wykresu w formie tabeli przestawnej.
pd.options.display.float_format = '{:,.1f}'.format
paymenttype1_plot_ri_tab = pd.pivot_table(all_coop, values= 'to_count_x', index=['paymenttype'], columns=['year'],\
aggfunc={'to_count_x':np.sum}, fill_value='0',margins=True, margins_name='Sum')
paymenttype1_plot_ri_tab.sort_values(by='Sum', ascending=False, axis=0)
| year | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | Sum |
|---|---|---|---|---|---|---|---|---|---|
| paymenttype | |||||||||
| Sum | 10064 | 23174 | 33364 | 28945 | 36767 | 71772 | 54641 | 22126 | 280853 |
| VI | 3,067.0 | 8,816.0 | 13,786.0 | 12,056.0 | 15,142.0 | 27,814.0 | 21,235.0 | 8,486.0 | 110402 |
| AE | 1,831.0 | 5,831.0 | 9,007.0 | 7,853.0 | 9,095.0 | 17,763.0 | 14,867.0 | 5,915.0 | 72162 |
| MC | 1,577.0 | 4,385.0 | 7,345.0 | 7,027.0 | 9,903.0 | 18,867.0 | 14,705.0 | 6,041.0 | 69850 |
| DB | 3,390.0 | 3,649.0 | 2,444.0 | 1,328.0 | 1,542.0 | 2,286.0 | 1,724.0 | 1,139.0 | 17502 |
| OC | 199.0 | 493.0 | 782.0 | 681.0 | 1,085.0 | 4,953.0 | 1,854.0 | 515.0 | 10562 |
| ?? | 0 | 0 | 0 | 0 | 0 | 89.0 | 256.0 | 30.0 | 375 |
Klienci którzy posiadają najniższą subskrybcję, generują największy przychód dla badanej jednostki.
subscribers1 = subscribers.rename(columns={'customer_id':'customerid'}, inplace=False)
totalprice_subscribers = all_coop.merge(subscribers1,left_on='customerid',right_on='customerid')
totalprice_subscribers_plot = totalprice_subscribers[['totalprice_y','rate_plan']].groupby(by='rate_plan').sum()
totalprice_subscribers_plot
| totalprice_y | |
|---|---|
| rate_plan | |
| Bottom | 200,303.3 |
| Middle | 20,068.5 |
| Top | 19,131.6 |
Średnia arytmetyczna jest jednym z najpopularniejszych pojęć statystycznych, służąca do opisu zbiorowości. Liczona jest dla danych numerycznych poprzez dodanie wszystkich wartości badanych zmiennych, a następnie podzielenie tej sumy przez liczebność populacji. Liczymy ją z wzoru ogólnego o następującej postaci.
Drugim bardzo popularnym pojąciem jest odchylenie standardowe, które opisuje jak wartości są rozrzucone wokół średniej arytmetycznej. Im wyższa wartość odchylenia standardowego, tym wartości obserwacji są bardziej oddalone od średniej i odwrotnie im niższa wartość odchylenia standardowego, tym wartości obserwacji są bardzie skupione wokół średniej. Odchylenie standardowe z próby lub dyspersja z próby liczone jest z następującego wzoru:
Median czyli wartość środkowa cechy w uporządkowanym niemalejąco ciągu obserwacji. Dzieli zbiór na dwie równoliczne części. Liczona jest na podstawie poniższego wzoru:
Aby zbadać podstawowe wielkości sprzedażowe, dokonuje wyciecia zbędnych dla tego celu danych, a następnie obliczam podstawowe wartości statystyczne. Dowiaduję się dzięki temu o wielkości całkowitej sprzedaży, która jest na poziomie 435 384 sztuk oraz jej całkowietj wartości na poziomie 13 708 896,30 dolarów. Ponadto obliczam podstawowe wartości statystyczne danych sprzedażowych dla badanego okresu. Średnia wartośc sprzedaży kształtuje się na poziomie 71 dolarów z otchyleniem standardowym 183,51. Najwyższe zamówienie opiewało na kwotę 9848,96 dolara. Mediana kształtuje się na poziomie cenowym wynoszącym 25,90 dolara, co oznacza, że połowa zawartych transakcji nie przekraczała kwoty 25,90 dolara.
orders_stat = orders[['totalprice', 'numorderlines', 'numunits', 'to_count']]
print(orders.totalprice.sum())
print(orders.numunits.sum())
13708896.3 435384
orders_stat.describe()
| totalprice | numorderlines | numunits | to_count | |
|---|---|---|---|---|
| count | 192,983.0 | 192,983.0 | 192,983.0 | 192,983.0 |
| mean | 71.0 | 1.5 | 2.3 | 1.0 |
| std | 183.5 | 1.2 | 63.4 | 0.0 |
| min | 0.0 | 1.0 | 1.0 | 1.0 |
| 25% | 16.0 | 1.0 | 1.0 | 1.0 |
| 50% | 25.9 | 1.0 | 1.0 | 1.0 |
| 75% | 46.9 | 2.0 | 2.0 | 1.0 |
| max | 9,849.0 | 150.0 | 19,527.0 | 1.0 |
Analizując całkowitą wartość sprzedaży z podziałem na lata widać, że do 2014 roku badana jednostka notowała nieustanny wzrost przychodów. Jednakże w kolejnych latach zanotowany trend, wskazuje na drastyczne pogorszenie sytuacji.
pd.options.display.float_format = '${:,.2f}'.format
orderlines.drop(columns=['orderlineid','orderid','productid']).groupby(by=['year']).sum()
| unitprice | numunits | totalprice | |
|---|---|---|---|
| year | |||
| 2009 | $210,287.86 | 15775 | $256,191.15 |
| 2010 | $822,656.99 | 28905 | $908,772.88 |
| 2011 | $1,305,502.62 | 62755 | $1,424,813.41 |
| 2012 | $1,313,670.46 | 36258 | $1,394,676.35 |
| 2013 | $2,054,562.42 | 48156 | $2,151,596.99 |
| 2014 | $2,718,593.13 | 83021 | $2,826,945.30 |
| 2015 | $2,800,334.91 | 80693 | $2,920,033.71 |
| 2016 | $1,779,475.25 | 79821 | $1,825,866.51 |
orderlines_by_year = orderlines.drop(columns=['orderlineid','orderid','productid']).groupby(by=['year']).sum()
orderlines_by_year.plot.line(y='totalprice',use_index =True, color = 'red', alpha = 0.7, figsize = (13,6))
plt.title('Wartość sprzedaży w kolejnych latach', color = 'black', fontsize=16, )
plt.ylabel('Wartość sprzedaży w mln $', fontsize=12, color= 'black')
plt.xlabel('Lata', fontsize=12, color= 'black')
plt.grid(True)
Na poniższym wykersie oraz danych w tabeli pokazano przychód z podziałem na płeć klientów. Zauważalnie wyższe przychody generują mężczyźni, co koreluje ze strukturą klientów.
m_w = all_coop.groupby(['year','gender']).sum().totalprice_y
m_w_plot = pd.DataFrame(m_w)
m_w_plot_ri = m_w_plot.reset_index()
m_w_plot_ri['gender'].replace({'F':'Female', 'M':'Man',}, inplace = True)
fig = px.line(m_w_plot_ri, x = 'year', y = 'totalprice_y', color = 'gender')
fig.update_layout(barmode = 'group')
fig.layout.title = 'Wielkość sprzedaży z podziałem na płeć'
fig.layout.xaxis.title = 'Lata'
fig.layout.yaxis.title = 'Wartość sprzedaży w mln $'
fig.show()
Dane z wykresu powyżej przedstawione w postacie tabeli przestawnej.
pd.options.display.float_format = '{:,.2f}$'.format
PGbN_plot_ri_tab = pd.pivot_table(all_coop, values= 'totalprice_y', index=['gender'], columns=['year'],\
aggfunc={'totalprice_y':np.sum}, fill_value='0',margins=True, margins_name='Sum')
PGbN_plot_ri_tab.sort_values(by='Sum', ascending=False, axis=0)
| year | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | Sum |
|---|---|---|---|---|---|---|---|---|---|
| gender | |||||||||
| Sum | 236,639.35$ | 864,008.88$ | 1,261,123.72$ | 1,263,577.16$ | 1,919,386.96$ | 2,816,512.97$ | 2,368,412.90$ | 1,556,264.24$ | 12,285,926.18$ |
| M | 148,979.06$ | 542,677.34$ | 788,116.34$ | 809,416.34$ | 1,112,419.42$ | 1,690,865.80$ | 1,433,780.36$ | 1,002,436.40$ | 7,528,691.06$ |
| F | 87,660.29$ | 321,331.54$ | 473,007.38$ | 454,160.82$ | 806,967.54$ | 1,125,647.17$ | 934,632.54$ | 553,827.84$ | 4,757,235.12$ |
Wielkość sprzedaży w kolejnych latach, z podziałem na grupy produktowe. W badanym okresie, widać, że w kolejnych latach niezmiennie w sprzedaży wiodą prym produkty z grupy ARTWORK. Bardzo widoczny jest również trend, który wskazuje rok 2014 jako przełomowy co do wielkości sprzedaży, ponieważ w kolejnych latach zauważalny jest spadek sprzedaży praktycznie we wszytkich grupach produktowych.
PGbN = all_coop.groupby(['year','productgroupname']).sum().totalprice_y
PGbN_plot = pd.DataFrame(PGbN)
PGbN_plot_ri = PGbN_plot.reset_index()
fig = px.bar(PGbN_plot_ri, x = 'year', y = 'totalprice_y', color = 'productgroupname',\
color_discrete_sequence= px.colors.qualitative.Dark2)
fig.update_layout(barmode = 'group')
fig.layout.title = 'Wielkość sprzedaży w kolejnych latach z podziałem na grupy produktowe'
fig.layout.xaxis.title = 'Lata'
fig.layout.yaxis.title = 'Wartość sprzedaży w mln $'
fig.show()
Dane z wykresu powyżej przedstawione w tabeli przestawnej.
pd.options.display.float_format = '{:,.2f}$'.format
PGbN_plot_ri_tab = pd.pivot_table(all_coop, values= 'totalprice_y', index=['productgroupname'], columns=['year'],\
aggfunc={'totalprice_y':np.sum}, fill_value='0',margins=True, margins_name='Sum')
PGbN_plot_ri_tab.sort_values(by='Sum', ascending=False, axis=0)
| year | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | Sum |
|---|---|---|---|---|---|---|---|---|---|
| productgroupname | |||||||||
| Sum | 259,303.72$ | 959,607.31$ | 1,366,999.26$ | 1,385,445.37$ | 2,114,341.67$ | 3,060,695.80$ | 2,556,896.99$ | 1,693,349.50$ | 13,396,639.62$ |
| ARTWORK | 154,924.25$ | 551,489.25$ | 801,835.07$ | 874,250.57$ | 1,411,443.08$ | 1,975,516.28$ | 1,814,277.30$ | 1,358,520.68$ | 8,942,256.48$ |
| BOOK | 79,823.50$ | 253,704.54$ | 314,702.99$ | 335,189.71$ | 466,927.18$ | 434,543.70$ | 348,744.78$ | 177,628.17$ | 2,411,264.57$ |
| OCCASION | 22,937.44$ | 57,000.80$ | 171,355.29$ | 95,562.20$ | 104,241.61$ | 289,103.93$ | 181,512.50$ | 80,155.81$ | 1,001,869.58$ |
| GAME | 0 | 0 | 1,242.91$ | 9,248.37$ | 46,739.74$ | 181,053.14$ | 73,778.92$ | 41,846.49$ | 353,909.57$ |
| CALENDAR | 899.43$ | 18,322.48$ | 42,071.61$ | 46,716.51$ | 48,705.23$ | 48,508.46$ | 47,344.13$ | 11,948.25$ | 264,516.10$ |
| OTHER | 0 | 76,380.64$ | 30,858.39$ | 3,295.12$ | 14,527.14$ | 48,230.24$ | 40,903.56$ | 8,917.93$ | 223,113.02$ |
| APPAREL | 719.10$ | 2,709.60$ | 4,933.00$ | 21,182.89$ | 21,757.69$ | 83,740.05$ | 50,335.80$ | 14,332.17$ | 199,710.30$ |
| FREEBIE | 0 | 0.00$ | 0.00$ | 0 | 0 | 0.00$ | 0.00$ | 0.00$ | 0.00$ |
Wielkość sprzedaży w kolejnych latach, z podziałem na formy płatności. W badanym okresie, widać, że w kolejnych latach niezmiennie największy udział mają trzy formy płatności IV, AE i MC.
paymenttype_in_year = all_coop.groupby(['year','paymenttype']).sum().totalprice_y
paymenttype_in_year_plot = pd.DataFrame(paymenttype_in_year)
paymenttype_in_year_plot_ri = paymenttype_in_year_plot.reset_index()
plot = px.line(paymenttype_in_year_plot_ri,x='year',y='totalprice_y',color='paymenttype')
plot.update_layout(barmode = 'group')
plot.layout.title = 'Wielkość sprzedaży z podziałem na formy płatności'
plot.layout.xaxis.title = 'Lata'
plot.layout.yaxis.title = 'Wartość sprzedaży w mln $'
plot.show()
pd.options.display.float_format = '{:,.2f}$'.format
paymenttype_in_year_plot_ri_tab = pd.pivot_table(all_coop, values= 'totalprice_y', index=['paymenttype'], columns=['year'],\
aggfunc={'totalprice_y':np.sum}, fill_value='0',margins=True, margins_name='Sum')
paymenttype_in_year_plot_ri_tab.sort_values(by='Sum', ascending=False, axis=0)
| year | 2009 | 2010 | 2011 | 2012 | 2013 | 2014 | 2015 | 2016 | Sum |
|---|---|---|---|---|---|---|---|---|---|
| paymenttype | |||||||||
| Sum | 259,303.72$ | 959,607.31$ | 1,366,999.26$ | 1,385,445.37$ | 2,114,341.67$ | 3,060,695.80$ | 2,556,896.99$ | 1,693,349.50$ | 13,396,639.62$ |
| VI | 82,974.40$ | 341,586.80$ | 542,782.78$ | 522,814.75$ | 794,102.23$ | 1,092,583.84$ | 909,132.29$ | 585,829.93$ | 4,871,807.02$ |
| AE | 61,521.17$ | 285,296.89$ | 457,812.94$ | 486,952.67$ | 684,201.85$ | 1,060,100.83$ | 918,375.30$ | 631,963.34$ | 4,586,224.99$ |
| MC | 40,559.23$ | 162,755.66$ | 288,477.53$ | 317,132.10$ | 562,548.83$ | 799,950.13$ | 656,074.56$ | 424,353.45$ | 3,251,851.49$ |
| DB | 70,560.62$ | 155,262.03$ | 52,695.98$ | 33,785.95$ | 26,385.99$ | 38,519.27$ | 27,555.32$ | 19,857.86$ | 424,623.02$ |
| OC | 3,688.30$ | 14,705.93$ | 25,230.03$ | 24,759.90$ | 47,102.77$ | 69,488.08$ | 44,733.79$ | 31,240.13$ | 260,948.93$ |
| ?? | 0 | 0 | 0 | 0 | 0 | 53.65$ | 1,025.73$ | 104.79$ | 1,184.17$ |
Obserwacje odstające - to mówiąc najprościej obserwację, które są relatywnie oddalone od pozostałych obserwacji. Oznaczać to może, że relacja pomiędzy zmiennymi objaśnianymi (zależnymi), a zmiennymi objaśniającymi (niezależnymi), dla danej obserwacji, może być inny niż od tego, który jest w pozostałym zbiorze danych. Mogą one odzwierciedlać stan faktyczny, ale również być efektem przypadku lub po prostu błędu pomiarowego.
Liczone są według nastepującego wzoru:
$$ {Obserwacje~Odstające}~ = [Q_1 - k(Q_3 - Q_1)~~, ~~Q_3 + k(Q_3 - Q_1)] $$gdzie k > 0 i najczęściej przyjmuje się wartość k = 1,5.
# wyrzucam zbędne kolumny pozostawiając tylko te z wartościami numerycznymi istotnymi dla obserwacji odstających
pd.options.display.float_format = '{:,.2f}'.format
all_coop_to_outliers = all_coop.drop(columns=['customerid', 'householdid', 'gender', 'firstname',\
'orderdate', 'city', 'state', 'zipcode', 'paymenttype', \
'numorderlines', 'shipdate', 'billdate', 'year',
'productname', 'productgroupcode', 'productgroupname', 'instockflag'])
Q1 = all_coop_to_outliers.quantile(0.25)
Q3 = all_coop_to_outliers.quantile(0.75)
iqr = Q3-Q1
lower = (Q1 - 1.5 * (iqr))
higher = (Q3 + 1.5 *(iqr))
outliers_l = (all_coop_to_outliers[iqr.index] < lower).sum()
outliers_h = (all_coop_to_outliers[iqr.index] > higher).sum()
outliers = pd.DataFrame ({'niska_granica': lower, 'wysoka_granica':higher,\
'wartości_odstające_L': outliers_l, 'wartości_odstające_H':outliers_h})
outliers
| niska_granica | wysoka_granica | wartości_odstające_L | wartości_odstające_H | |
|---|---|---|---|---|
| to_count_x | 1.00 | 1.00 | 0 | 0 |
| orderid | 749,221.00 | 1,564,981.00 | 0 | 11016 |
| campaignid | 1,998.50 | 2,378.50 | 0 | 0 |
| totalprice_x | -59.85 | 147.75 | 0 | 41167 |
| numunits_x | -2.00 | 6.00 | 0 | 13398 |
| to_count_y | 1.00 | 1.00 | 0 | 0 |
| orderlineid | 573,996.50 | 1,899,016.50 | 0 | 10233 |
| productid | 8,601.00 | 15,033.00 | 0 | 0 |
| unitprice | -15.50 | 52.50 | 0 | 37974 |
| numunits_y | 1.00 | 1.00 | 4901 | 19558 |
| totalprice_y | -19.87 | 59.92 | 0 | 39744 |
| fullprice | -14.50 | 61.50 | 0 | 40211 |
| to_count | 1.00 | 1.00 | 0 | 0 |
Współczynnik skośności lub astymetrii mówi o tym, jaka część odchylenia standardowego stanowi różnicę pomiędzy średnią arytmetyczną, a medianą. Współczynnik o wartości 0 jest to rozkład symetryczny. Wartość dodatnia oznacza rozkład prawostronnie skośny (średnia większa od mediany $\overline{x}>Me$) natomiast ujemna, lewostronnie skośny(średnia mniejsza od mediany - $\overline{x}<Me$).
Liczymy go z następującego wzoru:
gdzie:
$A_{me}~-~współczynnik~skośności~-~oparty~o~Medianę$
$\overline{x}~-~średnia~arytmetyczna$
$Me~-~mediana$
$s~-~odchylenie~standardowe$
pd.options.display.float_format = '{:,.2f}'.format
all_coop.drop(columns=['to_count_x','to_count_y','productname','to_count']).skew()
customerid -0.03 householdid 0.88 orderid 1.14 campaignid -0.54 totalprice_x 12.44 numorderlines 17.40 numunits_x 32.08 orderlineid 1.21 productid 0.45 unitprice 8.36 numunits_y 97.15 totalprice_y 9.32 year -0.47 fullprice 13.08 dtype: float64
Test Shapiro-Wilka - to jeden z najpopularniejszych testów służących do oceny czy zebrane dane należą do rozkładu nromalnego. Stosowany jest z reguły do mniejszych grup, przeważnie (N < 100), i dlatego przeprowadzę test dla grupy 100 losowo wybranych obserwacji.
Hipotezy formułowane w następujący sposób:
$H_0~~-~~próbka~pochodzi~ z~ rozkładu~ nromalnego$
$H_1~~-~~próbka~ nie~ pochodzi~ z ~rozkładu ~normalnego$
$$W=\frac{(∑_ia_i(n)(X_{n−i+1}−X_i))^2} {∑_{j=1}^n(X_j−\overline{X})^2}$$gdzie:
$ W~-~wynik~ testu~ Shapiro-Wilka$
$a_i(n)~-~stała,~wartość~tablicy$
$(X_{n−i+1}−X_i)~~róźnica~pomiędzy~skrajnymi~obserwacjami,~przy~czym~i=1~różnica~dla~min~i~max;~dla~i=2~różnica~dla~min+1~i~max~-1~itd. $
$j~-~kolejne~obserwacje~w~próbie$
$i~-~kolejne~różnice~między~skrajnymi~obserwacjami$
$\overline{X}~-~średnia$
data = all_coop[['orderid','numunits_x']]
data_gr = data.groupby(by='orderid').sum()
data_gr_list = data_gr['numunits_x'].tolist()
data_gr_list_test = random.sample(data_gr_list, 100)
W, p = sp.stats.shapiro(data_gr_list_test)
if (p < 0.05):
print("Dane nie pochodzą z rozkładu normalnego")
else:
print('Nie można odrzucicić hipotezy o normalności rozkładu')
print("W=",W," ","p=",p)
Dane nie pochodzą z rozkładu normalnego W= 0.35044795274734497 p= 5.059600660154502e-19
sns.kdeplot(data_gr_list_test, shade=True, color ='black',\
cumulative=False).set(title='Wykres danych testowych normalności rozkładu')
[Text(0.5, 1.0, 'Wykres danych testowych normalności rozkładu')]
Przeprowadzony test wskazuje przyjęcie: $~~~~~~H_1~~-~~próbka~ nie~ pochodzi~ z ~rozkładu ~normalnego$
Test Kołmogorowa-Smirnowa – test do oceny zgodności rozkładu analizowanych zmiennych z rozkładem normalnym. Test ten stosowany jest dla prób dużo większych, aniżeli te preferowane przez Test Shapiro-Wilka. Jako jedną z granic, podaje się N >100. W przeprowadzonym teście przyjmiemy próbę N = 1000. Test określany jest wzorem:
$$D_{n}=\sup_{x}|F_{n}(x)-F(x)|$$gdzie:
$F_n(x)$ - empiryczna dystrybuanta rozkładu normalnego wyliczana w poszczególnych punktach rozkładu, dla $n$-elementowej próby ,
$F(x)$ - teoretyczna dystrybuanta rozkładu normalnego.
Wynikiem testu jest para liczb $D$ i $p-wartość$ p
Hipotezy:
H0 : rozkład badanej cechy w populacji jest rozkładem normalnym
H1 : rozkład badanej cechy w populacji jest różny od rozkładu normalnego.
Wyznaczoną na podstawie statystyki testowej wartość p porównujemy z poziomem istotności α przyjętego na poziomie 0.05:
jeżeli p ≤α możemy odrzucić H0 przyjmując H1
jeżeli p >α nie ma podstaw do odrzucenia H0
data_KS = all_coop[['orderid','numunits_x']]
data_KS_gr = data.groupby(by='orderid').sum()
data_KS_gr_list = data_gr['numunits_x'].tolist()
data_KS_gr_list_test = random.sample(data_gr_list, 1000)
D, p = sp.stats.kstest(data_KS_gr_list_test,'norm', args=(np.mean(data_KS_gr_list_test),\
np.std(data_KS_gr_list_test,ddof=1)))
if (p < 0.05):
print("Dane nie pochodzą z rozkładu normalnego")
else:
print('Nie można odrzucicić hipotezy o normalności rozkładu')
print("D=",D," ","p=",p)
Dane nie pochodzą z rozkładu normalnego D= 0.4574358208036656 p= 7.840381517497464e-192
Z przeprowadzonych badań wynika, że badana jednostka gospodarcza w badanym okresie osiągnęła przychód na poziomie niespełna 14 mln dol. Największy udział w wielkości sprzedaży mają produkty z grupy ARTWORK generujące w badanym okresie przychód na poziomie nieco ponad 9 mln $, przy jednoczesnym udziale niespełna 20% w ilości wszystkich zawartych transakcji. Największy udział w ilości transakcji mają produkty z grupy BOOK i jest to niespełna 40% w badanym okresie. Przychód z tytułu sprzedaży produktów z tej grupy wynosi niespełna 2,5 mln dol. Rok 2014 był zdecydowanie najlepszy jeżeli chodzi o wielkość sprzedaży, w kolejnych latach, badana jednostka notowała spadki swoich przychodów w każdej grupie towarowej.
Najwyższą ilość transakcji odnotowano w miście Nowy York jak również wielkość sprzedaży z ogromną przewagą nad drugim w kolejności Brooklynem oraz pozostałymi miastami.
Spośród wszystkich form płatności najpopularniejsza jest VI z udziałem niespełna 40% wszystkich transakcji.
Większość pośród klientów stanowią mężczyźni, którzy stanowią 55,56%.
Przeprowadzono również testy Shapiro-Wilka oraz Kołmogorowa-Smironowa, które jednomyślnie wskazały, że posiadane dane nie pochodzą z rozkładu normalnego.
Analiza koszykowa jest wielowymiarową metodą statystyczną analizy danych, która pozwala na znajdowanie powiązań pomiędzy produktami z różnych kategorii. Rylacje zachodzące pomiędzy współwystępującymi elementami opisane są za pomocą reguł asocjacyjnych, wyrażonych na podstawie trzech miar: pokrycia reguły - Support (opisującej częstość występowania danej grupy elementów) oraz ufności reguły -Confidence (określającej prawdopodobieństwo wystąpienia jakiegoś zdarzenia po wystąpieniu innego zdarzenia) oraz przyrost - Lift - (określa czy fakty wystąpenia jednego produktu wpływa na zwiększenie prawdopodobieństwa pojawienia się drugiego produtku w ramach jednej transakcji)
$$SUPPORT~~~~supp(L)=\frac{nL}{n}$$$$CONFIDENCE~~~~conf(L\Longrightarrow{R})=\frac{supp(L+R)}{supp(L)}$$$$LIFT~~~~lift(L\Longrightarrow{R})=\frac{supp(L+R)}{supp(L)supp(R)}$$Ze względu na swoje właściwości, analiza reguł asocjacyjnych jest szczególnie użyteczna do analizy danych dotyczących wypadków przy pracy, ponieważ umożliwia identyfikację przebiegu najczęściej występujących wypadków przy pracy, przy jednoczesnym uwzględnieniu w analizie wszystkich etapów przebiegu. Oznacza to, że analizie nie jest poddawany każdy etap wypadku osobno, jak to ma miejsce w przypadku prostych zestawień statystycznych, lecz pełny przebieg wypadku. Należy jednak ograniczyć analizowane przebiegi wypadków przy pracy do kilku kluczowych zdarzeń, ponieważ uwzględnienie wszystkich etapów przebiegu (od procesu pracy, po rodzaj urazu i umiejscowienie urazu) powoduje znaczne rozproszenie wyników.
W przypadku zastosowania analizy reguł asocjacyjnych do danych dotyczących wypadków przy pracy pokrycie i ufność reguły należy rozumieć, jako: pokrycie reguły (wsparcie) – procent wypadków przy pracy, w których doszło do zdarzeń A i B, wśród wszystkich wypadków przy pracy. Oznacza częstość występowania tego typu wypadków przy pracy:
ufność reguły – jest to miara dokładności reguły, którą można określić jako procent wypadków przy pracy, w których doszło do powstania zdarzenia A i zdarzenia B, wśród wszystkich wypadków przy pracy, w których doszło do zdarzenia A. Oznacza prawdopodobieństwo wystąpienia zdarzenia B, jeżeli wystąpiło zdarzenie A:
Wobec poszukiwanych reguł asocjacyjnych warto wyznaczyć minimalne wymagania, które pozwalają na identyfikację najczęściej występujących wypadków przy pracy, a więc takich które mają odpowiednio duży udział w ogólnej liczbie wypadków, przy jednoczesnym zachowaniu odpowiedniej ich szczegółowości.
orderlines.head(2)
| orderlineid | orderid | productid | shipdate | billdate | unitprice | numunits | totalprice | year | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1010561 | 1006414 | 10834 | 2011-03-07 | 2011-03-08 | 18.00 | 1 | 18.00 | 2011 |
| 1 | 1010562 | 1006541 | 11052 | 2011-01-19 | 2011-01-20 | 10.00 | 2 | 20.00 | 2011 |
orderlines_sh_basket = orderlines.drop(columns=['orderlineid', 'shipdate', 'billdate','unitprice', 'numunits', 'totalprice', 'year'])
orderlines_sh_basket.head(4)
| orderid | productid | |
|---|---|---|
| 0 | 1006414 | 10834 |
| 1 | 1006541 | 11052 |
| 2 | 1006542 | 11070 |
| 3 | 1010154 | 11196 |
products.head(2)
| productid | productname | productgroupcode | productgroupname | instockflag | fullprice | |
|---|---|---|---|---|---|---|
| 0 | 10001 | nan | CA | CALENDAR | N | 15 |
| 1 | 10002 | nan | CA | CALENDAR | N | 10 |
products_sh_basket = products.drop(columns=['productname', 'productgroupcode','instockflag', 'fullprice'])
products_sh_basket.head(4)
| productid | productgroupname | |
|---|---|---|
| 0 | 10001 | CALENDAR |
| 1 | 10002 | CALENDAR |
| 2 | 10003 | CALENDAR |
| 3 | 10004 | BOOK |
sh_basket = pd.merge(orderlines_sh_basket, products_sh_basket, left_on='productid', right_on = 'productid')
sh_basket.head(2)
| orderid | productid | productgroupname | |
|---|---|---|---|
| 0 | 1006414 | 10834 | BOOK |
| 1 | 1008588 | 10834 | BOOK |
Połączone dane układam według grupy produktów oraz zliczam liczebność.
sh_basket_1 = sh_basket.groupby(['productgroupname','orderid']).count()
sh_basket_1.head(5)
| productid | ||
|---|---|---|
| productgroupname | orderid | |
| APPAREL | 1000865 | 1 |
| 1001025 | 1 | |
| 1001027 | 1 | |
| 1001096 | 1 | |
| 1001097 | 1 |
Poniższa tabela pokazuje czy dany produkt był zakupiony z innym, jeżeli wartość jest większa niże jeden, oznacza to, że produkt wystąpił kilka razy w ramach jednego zakupu. W następnym kroku zredukuję wszystkie wartości do 1.
sh_basket_2 = sh_basket_1.unstack().fillna(0).T.astype(int)
sh_basket_2
| productgroupname | APPAREL | ARTWORK | BOOK | CALENDAR | FREEBIE | GAME | OCCASION | OTHER | |
|---|---|---|---|---|---|---|---|---|---|
| orderid | |||||||||
| productid | 999992 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 999993 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| 999994 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| 999995 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| 999996 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | |
| 1643153 | 0 | 0 | 0 | 0 | 0 | 3 | 0 | 0 | |
| 1643154 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | |
| 1643155 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | |
| 1643156 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| 1643157 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
192983 rows × 8 columns
Po zmianie wszystkich zmiennych większych od 1 na 1 nasza tabela jest gotowa do dalszej analizy.
brak_zam = sh_basket_2.shape[0]
brak_zam
192983
sh_basket_2[sh_basket_2 > 1]=1
sh_basket_2
| productgroupname | APPAREL | ARTWORK | BOOK | CALENDAR | FREEBIE | GAME | OCCASION | OTHER | |
|---|---|---|---|---|---|---|---|---|---|
| orderid | |||||||||
| productid | 999992 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
| 999993 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| 999994 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| 999995 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| 999996 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | |
| 1643153 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 | |
| 1643154 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | |
| 1643155 | 0 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | |
| 1643156 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| 1643157 | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
192983 rows × 8 columns
Wyznaczam pierwszą miarę - Support - relację pomiędzy produktami, a następnie sprawdzam reguły asocjacyjne.
pd.options.display.float_format = '{:,.2f}'.format
freq_items = apriori(sh_basket_2, min_support=0.001, use_colnames=True)
freq_items.head(10)
| support | itemsets | |
|---|---|---|
| 0 | 0.06 | (APPAREL) |
| 1 | 0.24 | (ARTWORK) |
| 2 | 0.45 | (BOOK) |
| 3 | 0.05 | (CALENDAR) |
| 4 | 0.12 | (FREEBIE) |
| 5 | 0.06 | (GAME) |
| 6 | 0.20 | (OCCASION) |
| 7 | 0.03 | (OTHER) |
| 8 | 0.00 | (ARTWORK, APPAREL) |
| 9 | 0.00 | (BOOK, APPAREL) |
rules = association_rules(freq_items, metric="confidence", min_threshold=0.0001)
rules.head(15)
| antecedents | consequents | antecedent support | consequent support | support | confidence | lift | leverage | conviction | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | (ARTWORK) | (APPAREL) | 0.24 | 0.06 | 0.00 | 0.01 | 0.09 | -0.01 | 0.95 |
| 1 | (APPAREL) | (ARTWORK) | 0.06 | 0.24 | 0.00 | 0.02 | 0.09 | -0.01 | 0.78 |
| 2 | (BOOK) | (APPAREL) | 0.45 | 0.06 | 0.00 | 0.01 | 0.15 | -0.02 | 0.95 |
| 3 | (APPAREL) | (BOOK) | 0.06 | 0.45 | 0.00 | 0.07 | 0.15 | -0.02 | 0.59 |
| 4 | (FREEBIE) | (APPAREL) | 0.12 | 0.06 | 0.01 | 0.06 | 1.01 | 0.00 | 1.00 |
| 5 | (APPAREL) | (FREEBIE) | 0.06 | 0.12 | 0.01 | 0.12 | 1.01 | 0.00 | 1.00 |
| 6 | (GAME) | (APPAREL) | 0.06 | 0.06 | 0.00 | 0.02 | 0.31 | -0.00 | 0.96 |
| 7 | (APPAREL) | (GAME) | 0.06 | 0.06 | 0.00 | 0.02 | 0.31 | -0.00 | 0.96 |
| 8 | (OCCASION) | (APPAREL) | 0.20 | 0.06 | 0.00 | 0.01 | 0.11 | -0.01 | 0.95 |
| 9 | (APPAREL) | (OCCASION) | 0.06 | 0.20 | 0.00 | 0.02 | 0.11 | -0.01 | 0.82 |
| 10 | (OTHER) | (APPAREL) | 0.03 | 0.06 | 0.00 | 0.06 | 1.03 | 0.00 | 1.00 |
| 11 | (APPAREL) | (OTHER) | 0.06 | 0.03 | 0.00 | 0.03 | 1.03 | 0.00 | 1.00 |
| 12 | (ARTWORK) | (BOOK) | 0.24 | 0.45 | 0.02 | 0.08 | 0.17 | -0.09 | 0.60 |
| 13 | (BOOK) | (ARTWORK) | 0.45 | 0.24 | 0.02 | 0.04 | 0.17 | -0.09 | 0.80 |
| 14 | (ARTWORK) | (FREEBIE) | 0.24 | 0.12 | 0.02 | 0.08 | 0.70 | -0.01 | 0.96 |
pd.options.display.float_format = '{:,.1f}'.format
sh_basket_2_plot = rules.sort_values(by='lift',ascending=False).head(40)
sh_basket_2_plot.head(15)
| antecedents | consequents | antecedent support | consequent support | support | confidence | lift | leverage | conviction | |
|---|---|---|---|---|---|---|---|---|---|
| 76 | (OTHER) | (BOOK, OCCASION) | 0.0 | 0.0 | 0.0 | 0.0 | 3.4 | 0.0 | 1.0 |
| 73 | (BOOK, OCCASION) | (OTHER) | 0.0 | 0.0 | 0.0 | 0.1 | 3.4 | 0.0 | 1.1 |
| 60 | (BOOK, OCCASION) | (FREEBIE) | 0.0 | 0.1 | 0.0 | 0.3 | 2.3 | 0.0 | 1.2 |
| 65 | (FREEBIE) | (BOOK, OCCASION) | 0.1 | 0.0 | 0.0 | 0.0 | 2.3 | 0.0 | 1.0 |
| 40 | (OTHER) | (FREEBIE) | 0.0 | 0.1 | 0.0 | 0.2 | 2.1 | 0.0 | 1.2 |
| 41 | (FREEBIE) | (OTHER) | 0.1 | 0.0 | 0.0 | 0.1 | 2.1 | 0.0 | 1.0 |
| 36 | (GAME) | (FREEBIE) | 0.1 | 0.1 | 0.0 | 0.2 | 2.0 | 0.0 | 1.2 |
| 37 | (FREEBIE) | (GAME) | 0.1 | 0.1 | 0.0 | 0.1 | 2.0 | 0.0 | 1.1 |
| 66 | (BOOK, OTHER) | (FREEBIE) | 0.0 | 0.1 | 0.0 | 0.2 | 2.0 | 0.0 | 1.2 |
| 71 | (FREEBIE) | (BOOK, OTHER) | 0.1 | 0.0 | 0.0 | 0.0 | 2.0 | 0.0 | 1.0 |
| 59 | (FREEBIE) | (BOOK, GAME) | 0.1 | 0.0 | 0.0 | 0.0 | 2.0 | 0.0 | 1.0 |
| 54 | (BOOK, GAME) | (FREEBIE) | 0.0 | 0.1 | 0.0 | 0.2 | 2.0 | 0.0 | 1.2 |
| 38 | (OCCASION) | (FREEBIE) | 0.2 | 0.1 | 0.0 | 0.2 | 1.9 | 0.0 | 1.1 |
| 39 | (FREEBIE) | (OCCASION) | 0.1 | 0.2 | 0.0 | 0.4 | 1.9 | 0.0 | 1.3 |
| 67 | (BOOK, FREEBIE) | (OTHER) | 0.0 | 0.0 | 0.0 | 0.0 | 1.4 | 0.0 | 1.0 |
Dane w powyzszej tabeli - kolummna LIFT - wskazuje, że wystąpienie jednego produtku wpływa na zwiększenie prawdopodobieństwa wystąpienia drugiego produktu w ramach jednego zamówienia, dla wartości współczynnika wyższej niż 1. Produkt z grupy FREEBIE znacząco zwiększa sprzedaż artykułów z grup produktowych OCCASION, BOOK, OTHER, GAME, ARTWORK, co oznacza, że wręczane gratisy pozytywnie wpływają na sprzedaż. Oprócz tego widoczna jest pozytywna zależność z produktami z grupy OTHER oraz OCCASION i BOOK.
sh_basket_2_plot["rule"] = sh_basket_2_plot["antecedents"].apply(lambda x: ', '.join(list(x))).astype("unicode") + \
'->' + sh_basket_2_plot["consequents"].apply(lambda x: ', '.join(list(x))).astype("unicode")
sns.set_context('paper')
sns.set_theme(style='darkgrid')
sns.catplot(data=sh_basket_2_plot, x = 'confidence' , y = 'lift',
aspect = 2.5, hue = 'rule')
plt.xticks(rotation=60)
plt.show()
Przeprowadzona analiza badanej jednostki pozwoliła spojrzeć na nią z wielu różnych stron. Dowiedzieliśmy się między innymi jak wygląda struktura sprzedaży, klientów, grup produktowych, form płatności, rynków zbytu czy też przychodów. Zdobyte w ten sposób informacje, są doskonałym narzędziem dla zarządzających badaną jednostką. Dzięki nim mogą podjąć decyzję co do przyszłości jednostki. Wiedzą jakie produkty są dla nich strategiczne jeżeli chodzi o realizowaną sprzedaż, a nad którymi warto ‚popracować’, aby przynosiły większe zyski dla przedsiębiorstwa, które kanały zbytu są najefektywniejsze oraz jakie rynki zbytu są strategiczne. Wykazany spadek przychodów w ostatnich dwóch badanych latach, jest również istotną informacją, choćby do zgłębienia problemu dlaczego taki nastąpił, co miało na to wpływ, tym bardziej, że dotyczyło to wszystkich grup produktowych.
Przeprowadzona analiza koszykowa wskazała jasno relację pomiędzy grupami produktowymi, dzięki czemu wiadomo jak planować wszelkiego rodzaju promocje i inne akcje sprzedażowe, dzięki którym jednostka zwiększy swoje przychody.
Odpowiednie podejście do otrzymanych w drodze analizy danych, z pewnością pozytywnie wpłynie na działanie jednostki oraz jej przyszłość. Pozwoli na bardziej świadome podejmowanie przyszłych decyzji gospodarczych.